﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace Raider.WinForms.Actions.Design
{
    public class ActionManagerDesigner : ComponentDesigner
    {
        private ManagerEditor editor;
        private DesignerActionListCollection actionLists;

        /// <summary>
        /// Prepares the designer to view, edit, and design the specified component.
        /// </summary>
        /// <param name="component">The component for this designer.</param>
        public override void Initialize(IComponent component)
        {
            base.Initialize(component);

            // Acquire a reference to Visual Studio services.
            DesignerHost = (IDesignerHost)GetService(typeof(IDesignerHost));
            DesignerActionService = (DesignerActionService)GetService(typeof(DesignerActionService));
            SelectionService = (ISelectionService)GetService(typeof(ISelectionService));
            EventBindingService = (IEventBindingService)GetService(typeof(IEventBindingService));
            ActionUiService = (DesignerActionUIService)GetService(typeof(DesignerActionUIService));
            ComponentChangeService = (IComponentChangeService)GetService(typeof(IComponentChangeService));
            UIService = (IUIService)GetService(typeof(IUIService));

            // Hook up events.
            ComponentChangeService.ComponentAdded += ComponentAdded;
            ComponentChangeService.ComponentRemoving += ComponentRemoving;
            ComponentChangeService.ComponentChanged += ComponentChanged;

            // Set ToolStripRenderer to match VS style.
            ToolStripRenderer = (ToolStripRenderer)UIService.Styles["VsToolWindowRenderer"];
        }

        /// <summary>
        /// ActionManager that is being designed.
        /// </summary>
        public ActionManager ActionManager
        {
            get { return (ActionManager)Component; }
        }

        /// <summary>
        /// Gets the design-time action lists supported by the component associated with
        /// the designer.
        /// </summary>
        public override DesignerActionListCollection ActionLists
        {
            get
            {
                return actionLists ?? (actionLists = new DesignerActionListCollection
                    {
                        new ActionManagerActionList(Component)
                    });
            }
        }

        /// <summary>
        /// Gets the collection of components associated with the component managed by
        /// the designer.
        /// </summary>
        public override ICollection AssociatedComponents
        {
            get
            {
                return (ICollection)ActionManager.Actions;
            }
        }

        #region Visual Studio Services

        private IDesignerHost DesignerHost { get; set; }
        private DesignerActionService DesignerActionService { get; set; }
        private ISelectionService SelectionService { get; set; }
        private IEventBindingService EventBindingService { get; set; }
        private DesignerActionUIService ActionUiService { get; set; }
        private IUIService UIService { get; set; }
        private IComponentChangeService ComponentChangeService { get; set; }

        #endregion

        /// <summary>
        /// Gets tool strip renderer that matches VS style.
        /// </summary>
        public ToolStripRenderer ToolStripRenderer
        {
            get;
            private set;
        }

        // Save last editor's location & size.
        private static Point editorLocation;
        private static Size editorSize;

        public void EditActions()
        {
            editor = new ManagerEditor(this);
            editor.FormClosed += (s, e) => editor = null;
            editor.SizeChanged += (s, e) => editorSize = editor.Size;
            editor.LocationChanged += (s, e) => editorLocation = editor.Location;

            if (!editorSize.IsEmpty)
            {
                editor.Size = editorSize;
            }

            if (!editorLocation.IsEmpty)
            {
                editor.StartPosition = FormStartPosition.Manual;
                editor.Location = editorLocation;
            }

            editor.ShowDialog();
        }

        public ControlAction[] AddActions(string category, params Type[] actionTypes)
        {
            // Begin transaction.
            DesignerTransaction designerTransaction = DesignerHost.CreateTransaction("Add action");
            ComponentChangeService.OnComponentChanging(ActionManager, null);

            var list = new List<ControlAction>();
            foreach (var actionType in actionTypes)
            {
                // Create action.
                ControlAction action = (ControlAction)DesignerHost.CreateComponent(actionType);

                // Set category.
                if (category != null)
                {
                    var property = TypeDescriptor.GetProperties(action)["Category"];
                    property.SetValue(action, category);
                }

                // Add to collection.
                list.Add(action);
                ActionManager.Actions.Add(action);                
            }

            // Commit.
            ComponentChangeService.OnComponentChanged(ActionManager, null, null, null);
            designerTransaction.Commit();

            return list.ToArray();
        }

        public TAction AddAction<TAction>(string category) where TAction : ControlAction
        {
            return (TAction)AddActions(category, typeof(TAction))[0];
        }

        public void DeleteActions(params ControlAction[] actions)
        {
            var designerTransaction = DesignerHost.CreateTransaction("Delete action");
            foreach (var act in actions)
                DesignerHost.DestroyComponent(act);
            designerTransaction.Commit();
        }

        public void SelectAction(ControlAction action)
        {
            SelectionService.SetSelectedComponents(new[] { action });
        }

        public void ChangeCategory(string category, params ControlAction[] actions)
        {
            var designerTransaction = DesignerHost.CreateTransaction("Move action");

            foreach (var action in actions)
            {
                var property = TypeDescriptor.GetProperties(action)["Category"];
                property.SetValue(action, category);
            }

            designerTransaction.Commit();
            editor.CategoryChanged(category, actions);
        }

        public string GetDefaultEventName(IList<ControlAction> actions)
        {
            var commonEvent = TypeDescriptor.GetDefaultEvent(typeof(ControlAction));

            EventDescriptor defaultEvent = commonEvent;
            foreach (ControlAction action in actions)
            {
                var other = TypeDescriptor.GetDefaultEvent(action);
                if (other != defaultEvent && 
                    actions.All(a => TypeDescriptor.GetEvents(a).Contains(other)))
                {
                    defaultEvent = other;
                }
                else
                {
                    defaultEvent = commonEvent;
                    break;
                }
            }

            return defaultEvent != null ? defaultEvent.Name : null;
        }

        public void GenerateEventHandler(params ControlAction[] actions)
        {
            if (actions.Length == 0)
                return;

            var eventDesc = TypeDescriptor.GetDefaultEvent(actions[0]);
            var propDesc = EventBindingService.GetEventProperty(eventDesc);

            var designerTransaction = DesignerHost.CreateTransaction("Setting event");

            if (actions.Length == 1)
            {
                var existingHandler = propDesc.GetValue(actions[0]);
                if (existingHandler == null)
                {
                    string handler = EventBindingService.CreateUniqueMethodName(actions[0], eventDesc);
                    propDesc.SetValue(actions[0], handler);
                }
            }
            else
            {
                string handler = EventBindingService.CreateUniqueMethodName(actions[0], eventDesc);
                foreach (ControlAction action in actions)
                {
                    propDesc.SetValue(action, handler);
                }
            }
            
            designerTransaction.Commit();
            EventBindingService.ShowCode(actions[0], eventDesc);
        }

        private void ComponentAdded(object sender, ComponentEventArgs e)
        {
            ControlAction action = e.Component as ControlAction;
            if (action != null)
            {
                // Set default title.
                if (action.Title == null)
                {
                    var property = TypeDescriptor.GetProperties(e.Component)["Title"];
                    property.SetValue(e.Component, e.Component.Site.Name);
                }
            }
        }

        private void ComponentRemoving(object sender, ComponentEventArgs e)
        {
            // Dispose all ActionManager's actions.
            if (e.Component is ActionManager)
            {
                var manager = (ActionManager)e.Component;
                var actions = manager.Actions.ToArray();
                foreach (IComponent action in actions)
                {
                    DesignerHost.DestroyComponent(action);
                }
            }
        }

        private void ComponentChanged(object sender, ComponentChangedEventArgs e)
        {
            var controlAction = e.Component as ControlAction;
            if (controlAction != null && e.Member != null && editor != null)
            {
                switch (e.Member.Name)
                {
                    case "Name":
                        editor.NameChanged((string)e.NewValue, controlAction);
                        break;
                    case "Category":
                        if (!DesignerHost.InTransaction || DesignerHost.TransactionDescription != "Move action")
                        {
                            editor.CategoryChanged((string)e.NewValue, controlAction);
                        }
                        break;
                    case "Title":
                        editor.TitleChanged((string)e.NewValue, controlAction);
                        break;
                    case "Image":
                        editor.ImageChanged((Image)e.NewValue, controlAction);
                        break;
                }
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (editor != null)
                    editor.Dispose();

                // Unhook events.
                ComponentChangeService.ComponentAdding -= ComponentAdded;
                ComponentChangeService.ComponentRemoving -= ComponentRemoving;
                ComponentChangeService.ComponentChanged -= ComponentChanged;
            }

            base.Dispose(disposing);
        }
    }
}
